
use std::mem;
use std::ptr;
use std::error::Error;


#[derive(Debug)]
pub enum ByteBufferError {
    IndexOutBoundry,
    TruncateMessage,
}



const PAGE_SIZE: usize = 1024;

pub struct ByteBuffer {
    buf: Vec<u8>,
    read_idx: usize,
    write_idx: usize,
    prepend_size: u32, 
}


impl ByteBuffer {

    pub fn new(size: usize) -> Self {
        Self {
            buf: vec![0; size],
            read_idx: 0,
            write_idx: 0,
            prepend_size: 0,
        }
    }

    fn capacity(&self) -> usize {
        self.buf.capacity()
    }

    fn len(&self) -> usize {
        self.write_idx - self.read_idx
    }

    pub fn clear(&mut self) {
        self.buf.clear();
        self.write_idx = 0;
        self.read_idx = 0;
    }

    fn tail_space(&mut self, size: u32) -> bool {
        let s = size as usize;
        if self.write_idx + s >  self.capacity() { // need to expand the capacity
            if self.capacity() - self.len() < s {
                let extra_size = (s / PAGE_SIZE + 1) * PAGE_SIZE;
                let mut b = vec![0u8; self.capacity() + extra_size];
                let b_ptr = b.as_mut_ptr();
                unsafe {
                    ptr::copy_nonoverlapping(self.buf.as_ptr(), b_ptr, self.buf.len());
                }
                
                self.buf = b;
                true
            } else {
                unsafe {
                    let src = self.buf.as_ptr().offset(self.read_idx as isize);
                    let mut dest = Vec::<u8>::with_capacity(self.capacity());
                    let dest_ptr = dest.as_mut_ptr();
                    ptr::copy_nonoverlapping(src, dest_ptr , self.len());
                    self.buf = dest;
                    true
                }
            }
        } else {
            true
        }
    }

    fn move_read_index(&mut self, offset: u32) {
        let idx = self.read_idx + offset as usize;
        if idx > self.write_idx {
            println!("read index out of write index: {} > {}", idx, self.write_idx);
        } else {
            self.read_idx = idx;
        }
    }

    fn move_write_index(&mut self, offset: u32) {
        let idx = self.write_idx + offset as usize;
        if  idx > self.capacity() {
            println!("write index out of range: {} > {}", idx, self.len());
        } else {
            self.write_idx += offset as usize;
        }
        
    }

    pub fn write_compact_u32(&mut self, mut value: u32) {
        println!("write value: {}", value);
        while value >= 0x80 {
            self.tail_space(1);
            let v = ((value & 0x7F) | 0x80) as u8;
            self.buf[self.write_idx] = v;

            self.move_write_index(1);
            value >>= 7;
        }
        if value > 0 {
            self.tail_space(1);
            self.buf[self.write_idx] = value as u8;
            self.move_write_index(1);
        }
    }

    pub fn read_compact_u32(&mut self) -> Result<(u32, u32), ByteBufferError> {
        let buf_len = self.len();
        
        let check_buf_len = |len, check| -> Result<bool, ByteBufferError> {
            if len < check {
                return Err(ByteBufferError::TruncateMessage);
            } else {
                return Ok(true);
            }
        };
        
        if let Err(e) = check_buf_len(buf_len, 1) {
            return Err(e);
        }
        
        let mut size = 0;
        let mut result = 0u32;
        let mut tmp = self.buf[self.read_idx] as u32;
        
        let mut ret = Err(ByteBufferError::TruncateMessage);

        if tmp < 0x80 {
            ret = Ok((tmp, 1));
        } else {
            if let Err(e) = check_buf_len(buf_len, 2) {
                ret = Err(e);
            }

            result = tmp & 0x7F;
            tmp = self.buf[self.read_idx + 1] as u32;
            if tmp < 0x80 {
                result |=  tmp << 7;
                size = 2;
                ret = Ok((result, size));
            } else {
                if let Err(e) = check_buf_len(buf_len, 3) {
                    ret = Err(e);
                }
                
                result |= ((tmp&0x7f) << 7) as u32;
                tmp = self.buf[self.read_idx + 2] as u32;
                if tmp < 0x80 {
                    result |= tmp << 14;
                    size = 3;
                    ret = Ok((result, size));
                } else {
                    if let Err(e) = check_buf_len(buf_len, 4) {
                        ret = Err(e);
                    } 
                    result |= (tmp & 0x7F) << 14;
                    tmp = self.buf[self.read_idx + 3] as u32;
                    if tmp < 0x80 {
                        result |= tmp << 21;
                        size = 4;
                        ret = Ok((result, size));
                    } else {
                        if let Err(e) = check_buf_len(buf_len, 5)  {
                            ret = Err(e);
                        }
                        result |= (tmp & 0x7f) << 21;
                        tmp = self.buf[self.read_idx + 4] as u32;
                        if tmp < 0x10 {
                            result |= (tmp &0x7F) << 28;
                            size = 5;
                            ret = Ok((result, size));
                        } else {
                            ret = Err(ByteBufferError::TruncateMessage);
                        }
                    }
                }
            }
        }

        match ret {
            Ok((n,s)) => {
                self.move_read_index(s);
                return Ok((n,s));
            },
            Err(e) => {
                return Err(e)
            }
        }
    }
}


#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn it_works() {

        let mut buf = ByteBuffer::new(100);
        buf.write_compact_u32(100);
        if let Ok((s,n)) = buf.read_compact_u32() {
            assert_eq!(s, 100, "not equal");
            assert_eq!(n, 1);
        }

        buf.clear();
        buf.write_compact_u32(129);
        if let Ok((s,n)) = buf.read_compact_u32() {
            assert_eq!(s, 129, "not equal");
            assert_eq!(n, 2);
        }

        buf.clear();
        buf.write_compact_u32(49156);
        if let Ok((s,n)) = buf.read_compact_u32() {
            assert_eq!(s, 49156, "not equal");
            assert_eq!(n, 3);
        }

        // buf.clear();
        // buf.write_compact_u32(100);
        // if let Ok((s,n)) = buf.read_compact_u32() {
        //     assert_eq!(s, 100, "not equal");
        //     assert_eq!(n, 1);
        // }

        // buf.clear();
        // buf.write_compact_u32(100);
        // if let Ok((s,n)) = buf.read_compact_u32() {
        //     assert_eq!(s, 100, "not equal");
        //     assert_eq!(n, 1);
        // }
    }
}
